package com.lvcoding.cloud.auth.config;

import com.lvcoding.cloud.api.entity.SysUser;
import com.lvcoding.cloud.common.security.auth.CommonUser;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.*;

/**
 * @Description 授权服务器配置
 * @Date 2020-07-14 10:48 上午
 * @Author wuyanshen
 */
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    //认证管理器，密码模式必须配置
    private final AuthenticationManager authenticationManager;

    private final DataSource dataSource;

    private final RedisConnectionFactory redisConnectionFactory;

    private final ClientDetailsService clientDetailsService;


    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices tokenService = new DefaultTokenServices();
        tokenService.setTokenStore(tokenStore());
        tokenService.setClientDetailsService(clientDetailsService);
        tokenService.setSupportRefreshToken(true);

        // token增强链
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        tokenEnhancerChain.setTokenEnhancers(enhancers);

        // 自定义token数据内容
        enhancers.add(tokenEnhancer());
        tokenService.setTokenEnhancer(tokenEnhancerChain);
        
        return tokenService;
    }

    /**
     * 配置认证，端点服务，token存储方式和自定义token
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                //oauth2请求方式
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                //密码模式必须配置
                .authenticationManager(authenticationManager)
                .tokenServices(this.tokenServices());
    }



    /**
     * 配置客户端详情(基于JDBC存储客户端)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    /**
     * 配置端点的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                // /oauth/check_token公开
                .checkTokenAccess("permitAll()")
                .tokenKeyAccess("permitAll()")
                //表单认证
                .allowFormAuthenticationForClients();
    }


    /**
     * token存储方式(redis方式存储token)
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix("oauth:access:");
        return tokenStore;
    }

    /**
     * 自定义生成token
     */
    @Bean
    public TokenEnhancer tokenEnhancer(){
        return (accessToken, authentication) -> {
            if (authentication.getUserAuthentication() != null){
                DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) accessToken;
                Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
                CommonUser commonUser = (CommonUser) authentication.getUserAuthentication().getPrincipal();
                SysUser sysUser = commonUser.getSysUser();
                additionalInformation.put("user_id",sysUser.getId());
                additionalInformation.put("username",sysUser.getUsername());
                defaultOAuth2AccessToken.setAdditionalInformation(additionalInformation);
            }
            return accessToken;
        };
    }

    //@Bean
    //public TokenEnhancer tokenEnhancer(){
    //    return new CommonTokenEnhancer();
    //}

    //完成令牌端点endpoint配置以及令牌服务配置
    //@Override
    //public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //    endpoints.authenticationManager(authenticationManager)//密码模式必须配置
    //            //.userDetailsService(userDetailsService)//密码模式必须配置
    //            .authorizationCodeServices(this.authorizationCodeServices())//授权码模式必须配置
    //            .tokenServices(this.tokenServices())//令牌管理服务
    //            .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    //}

    //令牌管理服务
    //@Bean
    //public AuthorizationServerTokenServices tokenServices(){
    //    DefaultTokenServices tokenServices = new DefaultTokenServices();
    //    tokenServices.setClientDetailsService(clientDetailsService);
    //    tokenServices.setSupportRefreshToken(true);//设置支持令牌刷新
    //    tokenServices.setTokenStore(this.tokenStore());//设置令牌存储方式
    //    tokenServices.setAccessTokenValiditySeconds(7200);//设置令牌有效期2小时
    //    tokenServices.setRefreshTokenValiditySeconds(259200);//设置刷新令牌有效期3天
    //    return tokenServices;
    //}

    // 授权码模式的授权码存储方式，如果使用授权码模式就需要配这个
    //@Bean
    //public AuthorizationCodeServices authorizationCodeServices(){
    //    //内存方式存储
    //    return new InMemoryAuthorizationCodeServices();
    //    //jdbc方式存储
    //    //return new JdbcAuthorizationCodeServices(dataSource);
    //}

}
